/* Copyright (c) 2022 渝州大数据实验室
 *
 * Lanius is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *
 *     http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.yzbdl.lanius.orchestrate.serv.service.task.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.yzbdl.lanius.orchestrate.serv.dto.resource.ServerProgramInfoDTO;
import org.yzbdl.lanius.orchestrate.serv.dto.task.IncrLogQueryDTO;
import org.yzbdl.lanius.orchestrate.serv.dto.task.TaskInstanceQueryDTO;
import org.yzbdl.lanius.orchestrate.serv.dto.task.TaskInstanceResourceDTO;
import org.yzbdl.lanius.orchestrate.serv.entity.task.TaskInstance;
import org.yzbdl.lanius.orchestrate.serv.entity.task.TaskPlan;
import org.yzbdl.lanius.orchestrate.serv.enums.TaskInstanceStatusEnum;
import org.yzbdl.lanius.orchestrate.serv.mapper.resource.ServerProgramMapper;
import org.yzbdl.lanius.orchestrate.serv.mapper.task.TaskInstanceMapper;
import org.yzbdl.lanius.orchestrate.serv.mapper.task.TaskPlanMapper;
import org.yzbdl.lanius.orchestrate.serv.quartz.handler.category.ProgramCategoryExecutorFactory;
import org.yzbdl.lanius.orchestrate.serv.quartz.handler.category.ProgramCategoryExecutorInvokerHandler;
import org.yzbdl.lanius.orchestrate.serv.service.task.TaskInstanceService;
import org.yzbdl.lanius.orchestrate.serv.utils.CommonUtil;
import org.yzbdl.lanius.orchestrate.serv.utils.DateUtil;
import org.yzbdl.lanius.orchestrate.serv.utils.FileUtil;
import org.yzbdl.lanius.orchestrate.serv.utils.TaskInstanceLogUtil;
import org.yzbdl.lanius.orchestrate.serv.vo.task.IncrLogVO;
import org.yzbdl.lanius.orchestrate.serv.vo.task.ScheduleTaskInstanceVO;
import org.yzbdl.lanius.orchestrate.serv.vo.task.TaskInstanceVO;
import org.yzbdl.lanius.orchestrate.common.exception.runtime.BusinessException;
import org.yzbdl.lanius.orchestrate.common.utils.MessageUtil;
import org.yzbdl.lanius.orchestrate.common.utils.SpecialCharacterUtil;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

/**
 * 任务实例service实现类
 *
 * @author jinchunzhao@yzbdl.ac.cn
 * @date 2022-04-11 9:03
 */
@Slf4j
@Service
public class TaskInstanceServiceImpl extends ServiceImpl<TaskInstanceMapper, TaskInstance>
        implements TaskInstanceService {

    @Autowired
    private ServerProgramMapper serverProgramMapper;

    @Autowired
    private TaskPlanMapper taskPlanMapper;

    @Autowired
    private ThreadPoolExecutor executor;

    @Override
    public IPage<TaskInstanceVO> queryPage(Page<TaskInstanceVO> page, TaskInstanceQueryDTO instanceQueryDTO) {
        instanceQueryDTO.setKeyword(SpecialCharacterUtil.escapeStr(instanceQueryDTO.getKeyword()));
        long startIndex = page.getCurrent() * page.getSize() - page.getSize();
        long pageSize = page.getSize();
        IPage<TaskInstanceVO> iPage = this.baseMapper.queryPage(page, instanceQueryDTO, startIndex, pageSize);
        List<TaskInstanceVO> records = iPage.getRecords();
        if (CollectionUtils.isEmpty(records)) {
            return iPage;
        }
        // 遍历集合进行 时间戳转换为日、时、分、秒功能
        for (TaskInstanceVO taskInstanceVO : records) {
            String diff = taskInstanceVO.getDiff();
            String stamp = DateUtil.timeStampToDhms(Long.parseLong(diff) * 1000);
            taskInstanceVO.setDiff(stamp);
        }
        return iPage;
    }

    @Override
    public Boolean checkInstanceForRun(Long taskPlanId) {

        List<Integer> runStateList = CommonUtil.getRunTaskInstanceStatus();
        TaskInstance instance = this.baseMapper.getOneInstanceForRunIgnoreTenantId(runStateList, taskPlanId);
        return Objects.nonNull(instance);
    }

    @Override
    public List<ScheduleTaskInstanceVO> getSchedulerViewStatistics() {

        List<ScheduleTaskInstanceVO> schedulerViewStatistics = this.baseMapper.getSchedulerViewStatistics();
        // 获取系统数据库中所有的状态code信息
        List<Integer> status = schedulerViewStatistics.stream().map(ScheduleTaskInstanceVO::getStatus).distinct()
                .collect(Collectors.toList());

        // 获取枚举定义的状态code信息
        List<Integer> enumCodes = Arrays.stream(TaskInstanceStatusEnum.values()).map(TaskInstanceStatusEnum::getCode)
                .collect(Collectors.toList());

        // 获取两者的差集
        List<Integer> diffStatus =
                enumCodes.stream().filter(item -> !status.contains(item)).collect(Collectors.toList());

        // 差集不为空，进行默认数据填充
        if (CollectionUtils.isNotEmpty(diffStatus)) {
            ScheduleTaskInstanceVO scheduleTaskInstanceVO;
            BigInteger zero = BigInteger.ZERO;
            for (Integer stateCode : diffStatus) {
                scheduleTaskInstanceVO = ScheduleTaskInstanceVO.builder().status(stateCode).count(zero).build();
                schedulerViewStatistics.add(scheduleTaskInstanceVO);
            }
        }
        return schedulerViewStatistics;
    }

    @Override
    public TaskInstance getByIdIgnoreTenantId(Long taskInstanceId) {
        return this.baseMapper.getByIdIgnoreTenantId(taskInstanceId);
    }

    @Override
    public TaskInstanceResourceDTO getTaskInstanceResourceIgnoreTenantId(Long taskInstanceId) {
        return this.baseMapper.getTaskInstanceResourceIgnoreTenantId(taskInstanceId);
    }

    @Override
    public String getTransImageById(Long taskInstanceId, Long serverProgramId) {
        ServerProgramInfoDTO serverProgramEntity =
                serverProgramMapper.getServerProgramInfoByIdIgnoreTenantId(serverProgramId);
        ProgramCategoryExecutorInvokerHandler handler =
                ProgramCategoryExecutorFactory.getHandler(serverProgramEntity.getCategory().toString());
        if (Objects.isNull(handler)) {
            throw new BusinessException(MessageUtil.get("task.instance.image_not_found"));
        }

        return handler.transImage(taskInstanceId, serverProgramId);
    }

    @Override
    public IncrLogVO readIncrLog(HttpServletResponse response, Long page, Long size, IncrLogQueryDTO incrLogQueryDTO) {

        Long taskPlanId = incrLogQueryDTO.getTaskPlanId();
        Long taskInstanceId = incrLogQueryDTO.getTaskInstanceId();

        // 处理每页行数：默认及最大为100行
        size = Objects.isNull(size) ? 100 : size;
        size = (size > 100 || size < 0) ? 100 : size;

        // 页码（文件的指针位置）
        Long seek = (page <= 0) ? 0 : page;

        TaskPlan taskPlan = taskPlanMapper.selectById(taskPlanId);
        Long orgId = taskPlan.getOrgId();
        Long groupId = taskPlan.getGroupId();
        String logAbsolutePath = TaskInstanceLogUtil.getLogAbsolutePath(orgId, groupId, taskPlanId, taskInstanceId);

        Charset charset = StandardCharsets.UTF_8;
        try {
            return FileUtil.readIncrLogs(size, seek, charset, logAbsolutePath);
        } catch (IOException e) {
            log.error("读取任务实例日志文件异常", e);
            throw new BusinessException(MessageUtil.get("task.instance.file_log_error"));
        }
    }

    @Override
    public int updateByIdIgnoreTenantId(TaskInstance taskInstance) {
        return this.baseMapper.updateByIdIgnoreTenantId(taskInstance);
    }

    @Override
    public void deletedByTaskPlanId(Long orgId, Long taskGroupId, Long taskPlanId) throws Exception {

        LambdaQueryWrapper<TaskInstance> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TaskInstance::getTaskPlanId, taskPlanId);
        this.baseMapper.delete(queryWrapper);

        // 异步执行删除文件操作
        CompletableFuture.runAsync(() -> {
            try {
                TaskInstanceLogUtil.deleteLog(orgId, taskGroupId, taskPlanId);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }, executor);
    }
}
